Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement auto-switching to English when the option is enabled #3366

Merged
merged 22 commits into from
Mar 23, 2025

Conversation

Yusyuriv
Copy link
Member

@Yusyuriv Yusyuriv commented Mar 21, 2025

Warning

DO NOT MERGE YET. Highly experimental, needs thorough testing with both IME and non-IME languages; also needs someone to confirm there are no memory leaks with using the native Windows APIs.

Fixes #2108, fixes #2569, fixes #3126.

What it does

When the Always Start Typing in English Mode option is enabled, when the Flow Launcher window opens, it remembers the current keyboard layout and then switches to English. When the window hides, it restores the remembered keyboard layout.

Testing I've done

  • It correctly switches to English when the window opens and restores the original keyboard layout when the window hides.

Known issues

  • When it gets closed via Esc, Alt+Space, or Enter, it restores the keyboard layout correctly, but when the window loses focus, the layout does not get restored.

    I haven't found a way to fix this, but I think it's still better than not having this functionality at all.
    Fixed.

Things to consider

  • When used with a language that has an IME mode, don't switch the language, just disable the IME mode.

@Yusyuriv Yusyuriv added the bug Something isn't working label Mar 21, 2025
@Yusyuriv Yusyuriv self-assigned this Mar 21, 2025
@prlabeler prlabeler bot added the enhancement New feature or request label Mar 21, 2025
@Yusyuriv Yusyuriv marked this pull request as draft March 21, 2025 18:43

This comment has been minimized.

Copy link

gitstream-cm bot commented Mar 21, 2025

🥷 Code experts: onesounds, Jack251970

onesounds, Jack251970 have most 👩‍💻 activity in the files.
onesounds, Jack251970 have most 🧠 knowledge in the files.

See details

Flow.Launcher/ViewModel/MainViewModel.cs

Activity based on git-commit:

onesounds Jack251970
MAR 236 additions & 183 deletions 116 additions & 156 deletions
FEB 23 additions & 25 deletions 63 additions & 21 deletions
JAN 17 additions & 21 deletions
DEC 59 additions & 63 deletions
NOV 39 additions & 15 deletions
OCT

Knowledge based on git-blame:
onesounds: 14%
Jack251970: 12%

To learn more about /:\ gitStream - Visit our Docs

Copy link

gitstream-cm bot commented Mar 21, 2025

Be a legend 🏆 by adding a before and after screenshot of the changes you made, especially if they are around UI/UX.

Copy link
Contributor

coderabbitai bot commented Mar 21, 2025

📝 Walkthrough

Walkthrough

The changes add automatic keyboard layout management based on the user’s "StartWithEnglishMode" setting. When the main window is shown, the launcher switches to an English keyboard layout if enabled, and it restores the previous layout when hidden. Updates were made in both UI logic and underlying native method handling to support this behavior.

Changes

File(s) Change Summary
Flow.Launcher/ViewModel/MainViewModel.cs Adds logic in the Show and Hide methods to switch to an English keyboard layout (via Win32Helper.SwitchToEnglishKeyboardLayout) and restore the previous layout based on the StartWithEnglishMode setting.
Flow.Launcher/Infrastructure/NativeMethods.txt, Flow.Launcher/Infrastructure/Win32Helper.cs Restores and adds native method entries and constants, and implements methods (FindEnglishKeyboardLayout, SwitchToEnglishKeyboardLayout, RestorePreviousKeyboardLayout) to manage keyboard layout switching and restoration.

Assessment against linked issues

Objective Addressed Explanation
Keyboard layout switching to force English input mode (#2108, #2569, #3126)

Suggested labels

kind/i18n

Suggested reviewers

  • jjw24
  • taooceros

Poem

I’m a bunny tapping keys with glee,
Switching layouts so effortlessly.
When English mode hops on stage,
I restore the flow page by page.
In code and carrots, joy is true—
Hopping through changes, just for you!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
Flow.Launcher/Helper/KeyboardLayoutHelper.cs (4)

1-66: Good implementation of Windows APIs for keyboard layout management

The implementation correctly uses P/Invoke to access the necessary Windows APIs for keyboard layout management. The FindEnglishKeyboardLayout method effectively retrieves and filters keyboard layouts to identify English ones.

However, the current language detection method could be improved:

Consider using explicit English language IDs instead of string comparison for more robust language detection. The current approach using string contains "english" might not work reliably across all Windows versions and language configurations.

-    private const string LAYOUT_ENGLISH_SEARCH = "english";
+    // English language ID (0x0409 = US English, 0x0809 = UK English, etc.)
+    private static readonly uint[] ENGLISH_LANGUAGE_IDS = { 0x0409, 0x0809, 0x0c09, 0x1009, 0x1409, 0x1809, 0x1c09, 0x2009, 0x2409, 0x2809, 0x2c09, 0x3009, 0x3409 };

     private static IntPtr FindEnglishKeyboardLayout()
     {
         // Get the number of keyboard layouts
         var count = GetKeyboardLayoutList(0, null);
         if (count <= 0) return IntPtr.Zero;

         // Get all keyboard layouts
         var keyboardLayouts = new IntPtr[count];
         GetKeyboardLayoutList(count, keyboardLayouts);

         // Look for any English keyboard layout
         foreach (var layout in keyboardLayouts)
         {
             // The lower word contains the language identifier
             var langId = (uint)layout.ToInt32() & 0xFFFF;
-
-            // Get language name for the layout
-            var sb = new StringBuilder(256);
-            GetLocaleInfoA(langId, LOCALE_SLANGUAGE, sb, sb.Capacity);
-            var langName = sb.ToString().ToLowerInvariant();
-
-            // Check if it's an English layout
-            if (langName.Contains(LAYOUT_ENGLISH_SEARCH))
+            // Check if it's an English language ID
+            if (Array.IndexOf(ENGLISH_LANGUAGE_IDS, langId) >= 0)
             {
                 return layout;
             }
         }

         return IntPtr.Zero;
     }

68-87: Consider adding thread safety to the layout switching mechanism

The _previousLayout static field is shared and could cause race conditions if layout switching is triggered from multiple threads simultaneously.

Add thread synchronization to prevent potential race conditions:

-    private static IntPtr _previousLayout;
+    private static IntPtr _previousLayout;
+    private static readonly object _layoutLock = new object();

     public static void SetEnglishKeyboardLayout()
     {
-        // Find an installed English layout
-        var englishLayout = FindEnglishKeyboardLayout();
-
-        // No installed English layout found
-        if (englishLayout == IntPtr.Zero) return;
-
-        var hwnd = GetForegroundWindow();
-        var threadId = GetWindowThreadProcessId(hwnd, IntPtr.Zero);
-
-        // Store current keyboard layout
-        _previousLayout = GetKeyboardLayout(threadId) & 0xFFFF;
-
-        // Switch to English layout
-        ActivateKeyboardLayout(englishLayout, 0);
+        lock (_layoutLock)
+        {
+            // Find an installed English layout
+            var englishLayout = FindEnglishKeyboardLayout();
+
+            // No installed English layout found
+            if (englishLayout == IntPtr.Zero) return;
+
+            var hwnd = GetForegroundWindow();
+            var threadId = GetWindowThreadProcessId(hwnd, IntPtr.Zero);
+
+            // Store current keyboard layout
+            _previousLayout = GetKeyboardLayout(threadId);
+
+            // Switch to English layout
+            ActivateKeyboardLayout(englishLayout, 0);
+        }
     }

Also, the bitwise AND operation with 0xFFFF might be unnecessary when storing the layout handle. This operation extracts only the language ID portion but discards the keyboard type information.


89-94: Apply thread safety to SetPreviousKeyboardLayout method as well

Similar to the SetEnglishKeyboardLayout method, this method should also be thread-safe.

     public static void SetPreviousKeyboardLayout()
     {
-        if (_previousLayout == IntPtr.Zero) return;
-        ActivateKeyboardLayout(_previousLayout, 0);
-        _previousLayout = IntPtr.Zero;
+        lock (_layoutLock)
+        {
+            if (_previousLayout == IntPtr.Zero) return;
+            ActivateKeyboardLayout(_previousLayout, 0);
+            _previousLayout = IntPtr.Zero;
+        }
     }

7-95: Add error handling and logging for diagnostics

Since this is an experimental feature and involves Windows API calls that might fail, consider adding error handling and logging.

Add try-catch blocks and logging to help with debugging:

     public static void SetEnglishKeyboardLayout()
     {
+        try
+        {
             // Find an installed English layout
             var englishLayout = FindEnglishKeyboardLayout();

             // No installed English layout found
             if (englishLayout == IntPtr.Zero) return;

             var hwnd = GetForegroundWindow();
             var threadId = GetWindowThreadProcessId(hwnd, IntPtr.Zero);

             // Store current keyboard layout
             _previousLayout = GetKeyboardLayout(threadId) & 0xFFFF;

             // Switch to English layout
             ActivateKeyboardLayout(englishLayout, 0);
+        }
+        catch (Exception ex)
+        {
+            // Log the exception but don't crash the application
+            Log.Error("KeyboardLayoutHelper", $"Error switching to English layout: {ex.Message}", ex);
+        }
     }

     public static void SetPreviousKeyboardLayout()
     {
+        try
+        {
             if (_previousLayout == IntPtr.Zero) return;
             ActivateKeyboardLayout(_previousLayout, 0);
             _previousLayout = IntPtr.Zero;
+        }
+        catch (Exception ex)
+        {
+            // Log the exception but don't crash the application
+            Log.Error("KeyboardLayoutHelper", $"Error restoring previous layout: {ex.Message}", ex);
+        }
     }

Also consider adding a simple way to detect if an English layout is already active to avoid unnecessary switching:

public static bool IsEnglishLayoutActive()
{
    try
    {
        var hwnd = GetForegroundWindow();
        var threadId = GetWindowThreadProcessId(hwnd, IntPtr.Zero);
        var currentLayout = GetKeyboardLayout(threadId);
        var langId = (uint)currentLayout.ToInt32() & 0xFFFF;
        
        // Check if it's already an English language ID
        return Array.IndexOf(ENGLISH_LANGUAGE_IDS, langId) >= 0;
    }
    catch (Exception ex)
    {
        Log.Error("KeyboardLayoutHelper", $"Error checking if English layout is active: {ex.Message}", ex);
        return false;
    }
}
Flow.Launcher/ViewModel/MainViewModel.cs (2)

1377-1382: Good integration of keyboard layout switching on window show

The implementation correctly calls KeyboardLayoutHelper.SetEnglishKeyboardLayout() when StartWithEnglishMode is true, which fulfills the PR objective.

Consider adding a check to avoid unnecessary layout switching if English is already the active layout:

                if (StartWithEnglishMode)
                {
+                   // Only switch if not already using English layout
+                   if (!KeyboardLayoutHelper.IsEnglishLayoutActive())
                    KeyboardLayoutHelper.SetEnglishKeyboardLayout();
                }

This would require adding the IsEnglishLayoutActive() method to KeyboardLayoutHelper as suggested in the previous comment.


1377-1455: Consider adding exception handling for keyboard layout operations

Since keyboard layout operations involve native API calls that could potentially fail, adding try-catch blocks would make the code more robust.

Add try-catch blocks around keyboard layout operations to prevent exceptions from affecting the main application flow:

                if (StartWithEnglishMode)
                {
+                   try
+                   {
                        KeyboardLayoutHelper.SetEnglishKeyboardLayout();
+                   }
+                   catch (Exception ex)
+                   {
+                       Log.Error("MainViewModel", $"Error setting English keyboard layout: {ex.Message}", ex);
+                       // Continue execution despite the error
+                   }
                }

And similarly for the hide method:

            if (StartWithEnglishMode)
            {
+               try
+               {
                    KeyboardLayoutHelper.SetPreviousKeyboardLayout();
+               }
+               catch (Exception ex)
+               {
+                   Log.Error("MainViewModel", $"Error restoring previous keyboard layout: {ex.Message}", ex);
+                   // Continue execution despite the error
+               }
            }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dda008f and 81a4632.

📒 Files selected for processing (2)
  • Flow.Launcher/Helper/KeyboardLayoutHelper.cs (1 hunks)
  • Flow.Launcher/ViewModel/MainViewModel.cs (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: gitStream workflow automation
  • GitHub Check: gitStream workflow automation
  • GitHub Check: gitStream.cm
  • GitHub Check: gitStream.cm
  • GitHub Check: gitStream.cm
  • GitHub Check: gitStream.cm
  • GitHub Check: gitStream.cm
🔇 Additional comments (2)
Flow.Launcher/ViewModel/MainViewModel.cs (2)

1451-1455: Good implementation of layout restoration on window hide

The code appropriately restores the previous keyboard layout when hiding the window, fulfilling the PR objective.


17-17: Appropriate namespace inclusion

The using directive for Flow.Launcher.Helper is correctly added to access the new KeyboardLayoutHelper class.

This comment has been minimized.

This comment has been minimized.

@Jack251970
Copy link
Contributor

I have no idea how to test it. So could you please test it to check if PInvoke introduction has broken sth?

@Yusyuriv
Copy link
Member Author

I have no idea how to test it. So could you please test it to check if PInvoke introduction has broken sth?

Yes, with this version the keyboard layout switches to English, but seems to never switch back.

@Yusyuriv
Copy link
Member Author

Yusyuriv commented Mar 22, 2025

To test it, change your keyboard layout to a non-English one, then open Flow. It should switch to English. Close Flow. It should switch to the orignial layout.

@Jack251970
Copy link
Contributor

Get it

This comment has been minimized.

@Jack251970
Copy link
Contributor

Fixed. Now it works well.

This comment has been minimized.

This comment has been minimized.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
Flow.Launcher.Infrastructure/Win32Helper.cs (5)

326-331: Document the English language IDs

The array contains language IDs for different English variants, but it would be helpful to add a comment explaining what some of these values represent (e.g., US English, UK English, etc.) for better maintainability.

// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f
private static readonly uint[] EnglishLanguageIds =
{
-    0x0009, 0x0409, 0x0809, 0x0C09, 0x1009, 0x1409, 0x1809, 0x1C09, 0x2009, 0x2409, 0x2809, 0x2C09, 0x3009,
-    0x3409, 0x3C09, 0x4009, 0x4409, 0x4809, 0x4C09,
+    0x0009, // English (Neutral)
+    0x0409, // English (United States)
+    0x0809, // English (United Kingdom)
+    0x0C09, // English (Australia)
+    0x1009, // English (Canada)
+    0x1409, // English (New Zealand)
+    0x1809, // English (Ireland)
+    0x1C09, // English (South Africa)
+    0x2009, // English (Jamaica)
+    0x2409, // English (Caribbean)
+    0x2809, // English (Belize)
+    0x2C09, // English (Trinidad and Tobago)
+    0x3009, // English (Zimbabwe)
+    0x3409, // English (Philippines)
+    0x3C09, // English (Hong Kong SAR)
+    0x4009, // English (India)
+    0x4409, // English (Malaysia)
+    0x4809, // English (Singapore)
+    0x4C09, // English (Nigeria)
};

333-336: Document the IME language IDs

Similar to the English language IDs, adding comments to describe what languages these IME IDs represent would improve code maintainability.

private static readonly uint[] ImeLanguageIds =
{
-    0x0004, 0x7804, 0x0804, 0x1004, 0x7C04, 0x0C04, 0x1404, 0x0404, 0x0011, 0x0411, 0x0012, 0x0412,
+    0x0004, // Chinese (Neutral)
+    0x7804, // Chinese (Simplified, China)
+    0x0804, // Chinese (Simplified)
+    0x1004, // Chinese (Singapore)
+    0x7C04, // Chinese (Traditional, Hong Kong SAR)
+    0x0C04, // Chinese (Traditional, Hong Kong)
+    0x1404, // Chinese (Traditional, Macau SAR)
+    0x0404, // Chinese (Traditional, Taiwan)
+    0x0011, // Japanese (Neutral)
+    0x0411, // Japanese
+    0x0012, // Korean (Neutral)
+    0x0412, // Korean
};

343-370: Add error handling for GetKeyboardLayoutList

The method correctly handles the case when no keyboard layouts are found, but it doesn't check if the second call to GetKeyboardLayoutList fails. Consider adding error handling for this call as well.

private static unsafe HKL FindEnglishKeyboardLayout()
{
    // Get the number of keyboard layouts
    int count = PInvoke.GetKeyboardLayoutList(0, null);
    if (count <= 0) return HKL.Null;

    // Get all keyboard layouts
    var handles = new HKL[count];
    fixed (HKL* h = handles)
    {
-        _ = PInvoke.GetKeyboardLayoutList(count, h);
+        int result = PInvoke.GetKeyboardLayoutList(count, h);
+        if (result <= 0 || result != count)
+        {
+            throw new Win32Exception(Marshal.GetLastWin32Error());
+        }
    }

    // Look for any English keyboard layout
    foreach (var hkl in handles)
    {
        // The lower word contains the language identifier
        var langId = (uint)hkl.Value & KeyboardLayoutLoWord;

        // Check if it's an English layout
        if (EnglishLanguageIds.Contains(langId))
        {
            return hkl;
        }
    }

    return HKL.Null;
}

372-402: Add XML documentation for public method

Since SwitchToEnglishKeyboardLayout is a public method, it should have XML documentation to describe its purpose, parameters, and any exceptions it may throw.

+/// <summary>
+/// Switches the keyboard layout to English if available.
+/// </summary>
+/// <param name="backupPrevious">If true, the current keyboard layout will be stored for later restoration.</param>
+/// <exception cref="Win32Exception">Thrown when there's an error getting the window thread process ID.</exception>
public static unsafe void SwitchToEnglishKeyboardLayout(bool backupPrevious)
{
    // Function implementation...
}

Also, the comment for IME handling is informative, but could be made more visible as part of the method documentation.


404-416: Add XML documentation and NULL check for GetForegroundWindow

Similar to the previous method, RestorePreviousKeyboardLayout should have XML documentation. Additionally, add a check to ensure GetForegroundWindow() doesn't return null before sending a message to it.

+/// <summary>
+/// Restores the previously backed-up keyboard layout.
+/// </summary>
public static void RestorePreviousKeyboardLayout()
{
    if (_previousLayout == HKL.Null) return;

    var hwnd = PInvoke.GetForegroundWindow();
+    if (hwnd == HWND.Null) return;
    
    PInvoke.PostMessage(
        hwnd,
        PInvoke.WM_INPUTLANGCHANGEREQUEST,
        PInvoke.INPUTLANGCHANGE_FORWARD,
        _previousLayout.Value
    );
    _previousLayout = HKL.Null;
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf011f1 and 48aff32.

📒 Files selected for processing (2)
  • Flow.Launcher.Infrastructure/NativeMethods.txt (1 hunks)
  • Flow.Launcher.Infrastructure/Win32Helper.cs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Flow.Launcher.Infrastructure/NativeMethods.txt
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: gitStream.cm
🔇 Additional comments (3)
Flow.Launcher.Infrastructure/Win32Helper.cs (3)

3-3: LGTM: Added System.Linq for the Contains method

The addition of the System.Linq namespace is appropriate for the new code that uses the Contains method on arrays.


389-392: LGTM: Good handling of IME languages

The code correctly identifies IME languages and handles them specially, which aligns with the PR objective of properly supporting both IME and non-IME languages.


324-418: Overall implementation looks good

The implementation successfully addresses the PR objective of switching to English keyboard layout when Flow Launcher is opened and restoring the previous layout when closed. The code is well-structured, handles edge cases appropriately, and includes special handling for IME languages.

Some minor improvements could be made to documentation and error handling, but the core functionality appears solid.

This comment has been minimized.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
Flow.Launcher.Infrastructure/Win32Helper.cs (2)

376-401: Clarify the IME handling rationale with additional comments

The code intentionally returns early when an IME layout is detected instead of switching layouts. Although there is a brief comment about this behavior, it would be helpful to provide more context about why this special handling is necessary.

Consider adding a more detailed comment like:

 // If the current layout has an IME mode, disable it without switching to another layout.
 // This is needed because for languages with IME mode, Flow Launcher just temporarily disables
 // the IME mode instead of switching to another layout.
+// IME languages require specialized input method handling and fully switching them could disrupt
+// the user experience, so we maintain the existing behavior for backward compatibility.

402-411: Consider adding error handling for ActivateKeyboardLayout

The call to PInvoke.ActivateKeyboardLayout might fail, but there's no error checking or handling.

Consider adding error handling:

 // Switch to English layout
-PInvoke.ActivateKeyboardLayout(enHKL, 0);
+var result = PInvoke.ActivateKeyboardLayout(enHKL, 0);
+if (result == HKL.Null)
+{
+    // Log the error or handle the failure
+    var errorCode = Marshal.GetLastWin32Error();
+    System.Diagnostics.Debug.WriteLine($"Failed to activate keyboard layout: {errorCode}");
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48aff32 and 4df42a0.

📒 Files selected for processing (1)
  • Flow.Launcher.Infrastructure/Win32Helper.cs (3 hunks)
🔇 Additional comments (3)
Flow.Launcher.Infrastructure/Win32Helper.cs (3)

3-3: LGTM - Using System.Linq for array operations

The addition of System.Linq is necessary for the Contains() method used with the language ID arrays.


326-336: Well-documented language ID references

Good job providing the Microsoft documentation link for the language identifiers and clearly separating English from IME language IDs.


417-431: Good use of window-specific messaging for layout restoration

The implementation correctly posts the input language change message to the specific foreground window rather than broadcasting it system-wide, which aligns with previous review feedback.

This comment has been minimized.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
Flow.Launcher.Infrastructure/Win32Helper.cs (3)

327-336: Language ID arrays look good, consider adding a comment for selected IDs.

The implementation of the static readonly arrays for language IDs is appropriate and includes a helpful link to documentation.

Consider adding a brief comment explaining which languages are covered by the ImeLanguageIds array (Chinese variants, Japanese, Korean) to improve code readability for future maintainers.


384-401: Early return for IME layouts needs clearer documentation.

The code correctly identifies and handles IME layouts differently, but the comment could be more explanatory.

The comment on lines 393-395 mentions that "Flow Launcher just temporarily disables the IME mode instead of switching to another layout," but it doesn't explain why this approach is taken. Consider expanding the comment to clarify the rationale, which would help future developers understand this design decision.


402-411: Add error handling for ActivateKeyboardLayout.

The keyboard layout activation lacks error handling.

The call to ActivateKeyboardLayout at line 410 should include error handling to catch potential failures. Consider adding a check for the return value and throwing an exception or logging a warning if it fails:

-PInvoke.ActivateKeyboardLayout(enHKL, 0);
+var result = PInvoke.ActivateKeyboardLayout(enHKL, 0);
+if (result == HKL.Null)
+{
+    // Log warning or throw exception
+    throw new Win32Exception(Marshal.GetLastWin32Error());
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4df42a0 and 1bf5733.

📒 Files selected for processing (1)
  • Flow.Launcher.Infrastructure/Win32Helper.cs (3 hunks)
🔇 Additional comments (3)
Flow.Launcher.Infrastructure/Win32Helper.cs (3)

343-374: FindEnglishKeyboardLayout implementation is correct and robust.

The method correctly retrieves all keyboard layouts and finds the first English one based on the language ID.

The error handling at lines 354-357 has been correctly implemented to throw an exception when the API call fails (returns 0).


390-392: Process ID capture still missing in GetWindowThreadProcessId call.

The function doesn't capture the process ID returned by GetWindowThreadProcessId.

As noted in a previous review comment, GetWindowThreadProcessId is designed to return both the thread ID and the process ID via an out parameter. Consider capturing the process ID for enhanced robustness:

-var threadId = PInvoke.GetWindowThreadProcessId(PInvoke.GetForegroundWindow());
+uint processId;
+var threadId = PInvoke.GetWindowThreadProcessId(PInvoke.GetForegroundWindow(), out processId);

Even if you don't need the process ID now, capturing it follows the API's intended usage and improves future maintainability.


338-341:

✅ Verification successful

LGTM, but consider multi-window scenarios.

The constant and static field implementation is correct for storing and manipulating keyboard layouts.

Note that using a static field to store the previous layout might cause issues in multi-window scenarios. If multiple Flow Launcher windows can be opened simultaneously, they could interfere with each other's keyboard layout restoration. Can you verify if this is a valid concern for your application architecture?


🏁 Script executed:

#!/bin/bash
# Search for multi-window usage in the codebase
echo "Checking for multiple window instances or window management code:"
rg -p "new\s+(?:MainWindow|Window)\(" --type cs -A 2 -B 2

Length of output: 433


Action: Confirming static field usage is safe under the current single-window design.

After verifying with the code search, it appears that Flow Launcher currently creates only a single instance of MainWindow (see Flow.Launcher/App.xaml.cs, line 140). Therefore, while the use of a static field to store the previous keyboard layout could be problematic in a true multi-window scenario, the present architecture does not exhibit such behavior. If multi-window support is added in the future, this design decision should be revisited.

This comment has been minimized.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
Flow.Launcher.Infrastructure/Win32Helper.cs (1)

417-435: 🛠️ Refactor suggestion

Use SendMessage instead of PostMessage for reliable layout restoration.

Using PostMessage for keyboard layout restoration can lead to race conditions since it's asynchronous. If another keyboard layout change occurs before the message is processed, the restoration might not work as expected.

-            PInvoke.PostMessage(
-                hwnd,
-                PInvoke.WM_INPUTLANGCHANGEREQUEST,
-                PInvoke.INPUTLANGCHANGE_FORWARD,
-                _previousLayout.Value
-            );
+            PInvoke.SendMessage(
+                hwnd,
+                PInvoke.WM_INPUTLANGCHANGEREQUEST,
+                PInvoke.INPUTLANGCHANGE_FORWARD,
+                _previousLayout.Value
+            );
🧹 Nitpick comments (2)
Flow.Launcher.Infrastructure/Win32Helper.cs (2)

394-395: Consider capturing process ID for completeness.

The GetWindowThreadProcessId function returns both the thread ID and the process ID via an out parameter. Even if you don't need the process ID right now, capturing it would make the code more complete and maintainable.

-            var threadId = PInvoke.GetWindowThreadProcessId(hwnd);
-            if (threadId == 0) throw new Win32Exception(Marshal.GetLastWin32Error());
+            uint processId;
+            var threadId = PInvoke.GetWindowThreadProcessId(hwnd, out processId);
+            if (threadId == 0) throw new Win32Exception(Marshal.GetLastWin32Error());

428-433: Add error handling for message sending.

The code doesn't check if the message was successfully sent or handle potential errors. Consider adding error handling for the message sending operation.

-            PInvoke.PostMessage(
-                hwnd,
-                PInvoke.WM_INPUTLANGCHANGEREQUEST,
-                PInvoke.INPUTLANGCHANGE_FORWARD,
-                _previousLayout.Value
-            );
+            bool success = PInvoke.PostMessage(
+                hwnd,
+                PInvoke.WM_INPUTLANGCHANGEREQUEST,
+                PInvoke.INPUTLANGCHANGE_FORWARD,
+                _previousLayout.Value
+            );
+            
+            if (!success)
+            {
+                // Log error or handle the failure
+                var errorCode = Marshal.GetLastWin32Error();
+                // Could throw or log: new Win32Exception(errorCode)
+            }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1bf5733 and c63debe.

📒 Files selected for processing (2)
  • Flow.Launcher.Infrastructure/NativeMethods.txt (1 hunks)
  • Flow.Launcher.Infrastructure/Win32Helper.cs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Flow.Launcher.Infrastructure/NativeMethods.txt
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: gitStream.cm
🔇 Additional comments (5)
Flow.Launcher.Infrastructure/Win32Helper.cs (5)

3-3: Added System.Linq import for array operations.

The addition of the System.Linq namespace is appropriate for using the Contains method on arrays in the keyboard layout code.


11-11: Added KeyboardAndMouse namespace for keyboard API access.

Good addition of the Windows.Win32.UI.Input.KeyboardAndMouse namespace to access the keyboard-related Win32 APIs used in the new keyboard layout functionality.


324-337: Language ID constants are well-organized and documented.

The language ID constants for both English and IME languages are well-organized and properly documented with a link to official Microsoft documentation, which will help with future maintenance.


343-374: FindEnglishKeyboardLayout implementation looks correct.

The method properly retrieves all installed keyboard layouts and searches for an English one. The error handling has been fixed from previous reviews.


376-415: Good implementation of language switching with IME handling.

The implementation correctly finds an English layout, handles IME languages appropriately, and activates the English layout when needed. The comments clearly explain the special handling for IME languages.

This comment has been minimized.

This comment has been minimized.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
Flow.Launcher.Infrastructure/Win32Helper.cs (1)

389-404: 🛠️ Refactor suggestion

Use SendMessage instead of PostMessage for more reliable layout switching

Using PostMessage for keyboard layout restoration creates potential race conditions since it's asynchronous and doesn't wait for the message to be processed. There's also no error handling for the message sending.

Replace with SendMessage to ensure synchronous execution and add error handling:

-            PInvoke.PostMessage(
-                hwnd,
-                PInvoke.WM_INPUTLANGCHANGEREQUEST,
-                PInvoke.INPUTLANGCHANGE_FORWARD,
-                _previousLayout.Value
-            );
+            var result = PInvoke.SendMessage(
+                hwnd,
+                PInvoke.WM_INPUTLANGCHANGEREQUEST,
+                PInvoke.INPUTLANGCHANGE_FORWARD,
+                _previousLayout.Value
+            );
+            
+            if (result.Value == IntPtr.Zero)
+            {
+                var error = Marshal.GetLastWin32Error();
+                if (error != 0)
+                {
+                    System.Diagnostics.Debug.WriteLine($"Failed to restore keyboard layout: {new Win32Exception(error).Message}");
+                }
+            }
🧹 Nitpick comments (1)
Flow.Launcher.Infrastructure/Win32Helper.cs (1)

347-383: Method logic looks good, but add a configuration option

The implementation of SwitchToEnglishKeyboardLayout correctly finds and activates the English keyboard layout. The early exit for IME layouts is also appropriate as it maintains existing behavior.

Consider pulling the IME language tags into a configuration option that can be extended if needed, rather than hardcoding them:

-        private static readonly string[] ImeLanguageTags =
-        {
-            "zh", // Chinese
-            "ja", // Japanese
-            "ko", // Korean
-        };
+        private static readonly string[] ImeLanguageTags = Settings.Instance.ImeLanguageTags ?? new[] 
+        {
+            "zh", // Chinese
+            "ja", // Japanese
+            "ko", // Korean
+        };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4fc7f70 and d827d0a.

📒 Files selected for processing (2)
  • Flow.Launcher.Infrastructure/NativeMethods.txt (1 hunks)
  • Flow.Launcher.Infrastructure/Win32Helper.cs (3 hunks)
🔇 Additional comments (3)
Flow.Launcher.Infrastructure/Win32Helper.cs (2)

417-423: Error handling for GetKeyboardLayoutList is correct

The correction to check for result == 0 to detect an error is properly implemented.


453-489:

❓ Verification inconclusive

The registry-based approach for transient language identifiers is well-implemented

The method uses a thoughtful approach to handle transient language identifiers by checking the registry. The fallback to CultureInfo.GetCultureInfo is also appropriate.

Consider verifying that this code runs with proper permissions by testing on systems with restricted registry access:


🏁 Script executed:

#!/bin/bash
# Check if the registry key is accessible by running a similar access pattern
# This is to verify the code will work on systems with restricted registry access

echo "Checking registry access for keyboard layouts:"
rg "OpenSubKey.*UserProfile" --type cs -A 5 -B 5
echo ""
echo "Looking for other registry access patterns that might provide insights:"
rg "Registry\.CurrentUser\.OpenSubKey" --type cs -A 2 -B 2

Length of output: 5672


Registry Access Verification: Confirm Behavior Under Restricted Permissions

The registry-based approach for managing transient language identifiers in Flow.Launcher.Infrastructure/Win32Helper.cs (lines 453–489) remains well-implemented. The method appropriately checks the registry for transient identifiers and correctly falls back to CultureInfo.GetCultureInfo when needed. Notably, our repository-wide search confirms that similar registry access patterns exist (e.g., in Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs and Flow.Launcher/Helper/AutoStartup.cs), reinforcing consistency across the codebase.

However, please verify that systems with restricted registry access still grant the necessary permissions for these operations. Running targeted tests on such environments will provide additional confidence in the robustness of this implementation.

Flow.Launcher.Infrastructure/NativeMethods.txt (1)

51-61: Native method additions are complete and well-organized

The additions to NativeMethods.txt properly document all the Windows API functions and constants used in the new keyboard layout management functionality.

Copy link

@check-spelling-bot Report

🔴 Please review

See the 📂 files view, the 📜action log, or 📝 job summary for details.

❌ Errors Count
❌ forbidden-pattern 22
⚠️ ignored-expect-variant 1
⚠️ non-alpha-in-dictionary 19

See ❌ Event descriptions for more information.

If the flagged items are 🤯 false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
Flow.Launcher.Infrastructure/Win32Helper.cs (2)

389-404: Consider using SendMessage instead of PostMessage for layout restoration

The implementation uses PostMessage which is asynchronous and may introduce race conditions if other keyboard layout changes occur before the message is processed.

-            PInvoke.PostMessage(
-                hwnd,
-                PInvoke.WM_INPUTLANGCHANGEREQUEST,
-                PInvoke.INPUTLANGCHANGE_FORWARD,
-                _previousLayout.Value
-            );
+            PInvoke.SendMessage(
+                hwnd,
+                PInvoke.WM_INPUTLANGCHANGEREQUEST,
+                PInvoke.INPUTLANGCHANGE_FORWARD,
+                _previousLayout.Value
+            );

361-363: Capture Process ID for enhanced error handling

The current code only captures and validates the thread ID. For improved error handling and debugging, consider capturing the process ID as well.

-            var threadId = PInvoke.GetWindowThreadProcessId(hwnd);
-            if (threadId == 0) throw new Win32Exception(Marshal.GetLastWin32Error());
+            uint processId;
+            var threadId = PInvoke.GetWindowThreadProcessId(hwnd, out processId);
+            if (threadId == 0) throw new Win32Exception(Marshal.GetLastWin32Error());
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d827d0a and 4f2a951.

📒 Files selected for processing (1)
  • Flow.Launcher.Infrastructure/Win32Helper.cs (3 hunks)
🔇 Additional comments (3)
Flow.Launcher.Infrastructure/Win32Helper.cs (3)

325-342: Good keyboard layout management setup with clear organization

The new constants and static variables for keyboard layout management are well-organized and properly documented. Good use of language tags instead of hardcoded values and clear separation with a dedicated region.


344-383: Comprehensive keyboard layout switching implementation

The implementation properly handles the core functionality of switching to an English keyboard layout while preserving the previous layout. The early return for IME layouts (lines 370-376) aligns with the PR goal of ensuring backward compatibility for IME languages.


442-488: Well-documented approach to handling language tags

The GetLanguageTag method includes thorough documentation about potential issues with transient language identifiers and the workaround implemented. The warning about this approach potentially breaking in future Windows versions is appropriate.

@Jack251970 Jack251970 added this to the 1.20.0 milestone Mar 23, 2025
@Yusyuriv Yusyuriv merged commit ddec1bd into dev Mar 23, 2025
5 checks passed
@Yusyuriv Yusyuriv deleted the always-use-english branch March 23, 2025 14:58
@jjw24 jjw24 removed the enhancement New feature or request label Mar 24, 2025
@jjw24
Copy link
Member

jjw24 commented Mar 24, 2025

Highly experimental, needs thorough testing with both IME and non-IME languages; also needs someone to confirm there are no memory leaks with using the native Windows APIs.

@Yusyuriv I assume it's confirmed no memory leaks with the the Win API usage?

@Yusyuriv
Copy link
Member Author

@jjw24 As far as I can tell, there are no memory leaks, everything's properly disposed of. @Jack251970 Just in case, can you confirm this?

@Jack251970
Copy link
Contributor

@jjw24 @Yusyuriv I do not think these codes will cause any memory leak issues since HKL handles are managed by Windows and GetLanguageTag function is from Windows.Forms project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
3 participants